Introduction

From the PVSC44 TL sensitivity we concluded that:

  • Overal MACC data is higher than corresponding static or optimized $T_L$ which leads to low dyanamic predictions.
  • For at least 3 SURFRAD stations: bon, psu and sxf high $T_L$ in summer caused a seasonal bias in GHI predictions.
  • For the high elevation stations: tbl and dra, that also have very high DNI and low DHI, optimization of $T_L$ led to unphysical values, indicating that another factor may, such as sensor error, may be affecting predictions more than atmospheric conditions.

    In Ineichen's review paper [Ineichen, 2016], underestimation of all models was also attributed to MACC AOD values. Therefore, we attempt to scale the ECMWF AOD data with the static $T_L$ values to reduce the error, but allow the real-time data to be used with the Simplified Solis and Bird models that don't use $T_L$

References

Ineihen, Pierre, "Validation of Models that Estimate Clear Sky Global and Beam Solar Irradiance", Solar Energy 2016 DOI: 10.1016/j.solener.2016.03.017


In [1]:
# imports and settings
import os

import h5py
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import pvlib
import seaborn as sns
import statsmodels.api as sm
from scipy.optimize import minimize_scalar

from pvsc44_clearsky_aod import ecmwf_macc_tools

%matplotlib inline

sns.set_context('notebook', rc={'figure.figsize': (8, 6)})
sns.set(font_scale=1.5)


c:\python27\lib\site-packages\statsmodels\compat\pandas.py:56: FutureWarning: The pandas.core.datetools module is deprecated and will be removed in a future version. Please use the pandas.tseries module instead.
  from pandas.core import datetools

In [2]:
# get the "metadata" that contains the station id codes for the SURFRAD data that was analyzed
METADATA = pd.read_csv('metadata.csv', index_col=0)

In [3]:
# load calculations for each station
atm_params_3min_clear = {}
for station_id in METADATA.index:
    with h5py.File('%s_3min_clear_atm_params.h5' % station_id, 'r') as f:
        np_atm_params_3min_clear = pd.DataFrame(np.array(f['data']))
    np_atm_params_3min_clear['index'] = pd.DatetimeIndex(np_atm_params_3min_clear['index'])
    np_atm_params_3min_clear.set_index('index', inplace=True)
    np_atm_params_3min_clear.index.rename('timestamps', inplace=True)
    atm_params_3min_clear[station_id] = np_atm_params_3min_clear

In [4]:
# filter out low light

# CONSTANTS
MODELS = {'solis': 'SOLIS', 'lt': 'Linke', 'macc': 'ECMWF-MACC', 'bird': 'Bird'}
CS = ['dni', 'dhi', 'ghi']
LOW_LIGHT = 200  # threshold for low light in W/m^2

is_bright = {}
for station_id, station_atm_params_3min_clear in atm_params_3min_clear.iteritems():
    is_bright[station_id] = station_atm_params_3min_clear['ghi'] > LOW_LIGHT

In [23]:
TL_SENS = pd.read_csv('TL_sensitivity.csv')
TL_SENS


Out[23]:
Unnamed: 0 station lt_opt lt_ref month count mbe_ghi mbe_dni mbe_dhi opt_mbe_ghi opt_mbe_dni opt_mbe_dhi
0 0 bon 2.155085 2.355085 1 9129 -0.001547 0.019818 -0.233246 -0.019473 0.061968 -0.382488
1 1 bon 1.905000 2.655000 2 10781 -0.028635 -0.019283 -0.245600 0.003184 0.124124 -0.564924
2 2 bon 2.618238 2.955738 3 11380 -0.019267 0.000922 -0.124920 0.000631 0.047036 -0.256759
3 3 bon 2.805533 3.318033 4 16840 -0.034489 -0.037388 -0.039750 -0.000314 0.040061 -0.217297
4 4 bon 3.352459 4.352459 5 15367 -0.070550 -0.122929 0.137035 0.002310 0.003648 -0.076593
5 5 bon 3.496721 4.296721 6 17690 -0.061441 -0.109276 0.128928 0.001760 -0.005993 -0.051460
6 6 bon 3.807661 4.120161 7 19582 -0.037252 -0.056119 0.018070 -0.000327 -0.009347 -0.048644
7 7 bon 3.337705 4.137705 8 20634 -0.047119 -0.082604 0.017053 0.011332 0.023445 -0.181495
8 8 bon 3.121107 4.083607 9 20904 -0.053420 -0.113659 0.135163 -0.000736 0.004077 -0.122976
9 9 bon 2.892623 3.092623 10 17870 -0.023206 -0.075064 0.124854 -0.013717 -0.036153 -0.016330
10 10 bon 2.595082 2.645082 11 10725 0.013068 -0.016352 -0.053556 -0.003747 -0.018958 -0.122864
11 11 bon 2.150000 2.350000 12 7478 0.012747 0.022346 -0.231002 -0.005229 0.065277 -0.384984
12 12 tbl 0.103390 2.753390 1 15856 -0.000647 -0.072010 0.149118 -0.004263 0.617993 -1.469589
13 13 tbl -0.048333 2.951667 2 15515 -0.011306 -0.069641 0.182063 0.002348 0.618740 -1.551929
14 14 tbl 0.509836 3.059836 3 17880 -0.003036 -0.049582 0.228282 0.002821 0.293229 -1.101159
15 15 tbl 2.253279 3.653279 4 18907 -0.008384 -0.081893 0.386418 -0.001120 0.010326 -0.352099
16 16 tbl 3.928279 3.853279 5 17884 -0.001392 -0.078572 0.512081 -0.000375 -0.122579 0.185633
17 17 tbl 4.554918 4.054918 6 24830 0.011854 -0.084467 0.661087 0.001896 -0.148534 0.468863
18 18 tbl 5.066129 4.266129 7 24838 0.017739 -0.077490 0.630871 0.007455 -0.167151 0.494863
19 19 tbl 4.819262 3.944262 8 21735 0.027311 -0.055491 0.530432 -0.000180 -0.169925 0.446080
20 20 tbl 2.320082 3.595082 9 24651 0.019670 -0.074651 0.567098 0.000791 -0.020901 -0.259466
21 21 tbl 0.867623 3.292623 10 21589 0.006296 -0.096854 0.527843 -0.000851 0.182946 -0.850884
22 22 tbl 0.198361 2.848361 11 15970 0.002066 -0.082400 0.213301 -0.004410 0.516360 -1.389366
23 23 tbl -0.050000 2.750000 12 13237 -0.001379 -0.074925 0.012856 -0.001504 0.863685 -1.594119
24 24 dra -1.196610 2.403390 1 22720 0.012890 -0.018154 -0.046953 -0.003587 7.378684 -3.352147
25 25 dra -1.196667 2.603333 2 21403 -0.011460 -0.037520 0.037369 -0.007615 5.912255 -3.066793
26 26 dra -1.048361 2.801639 3 29261 -0.014927 -0.033570 0.053241 -0.004622 3.265560 -2.385856
27 27 dra -0.323361 2.901639 4 34852 -0.020921 -0.019563 -0.034294 -0.000831 0.784432 -1.537898
28 28 dra 0.203279 3.003279 5 44471 -0.018367 -0.024805 0.037533 -0.010367 0.383338 -1.202247
29 29 dra -0.048361 3.201639 6 53606 -0.025507 -0.051128 0.248912 0.001314 0.512256 -1.367968
... ... ... ... ... ... ... ... ... ... ... ... ...
54 54 gwn 4.150410 4.425410 7 13361 -0.029008 -0.028488 -0.039392 0.001287 0.015980 -0.098737
55 55 gwn 3.992623 4.242623 8 16546 -0.014980 0.021688 -0.112429 -0.002072 0.061791 -0.186378
56 56 gwn 1.929303 3.791803 9 21326 -0.026586 -0.060480 0.111333 -0.000347 0.223069 -0.551037
57 57 gwn 0.895082 3.295082 10 23934 -0.018480 -0.076596 0.233782 -0.002465 0.362481 -0.878415
58 58 gwn 0.200000 3.000000 11 19138 -0.014318 -0.092700 0.246239 0.004838 0.590787 -1.414712
59 59 gwn 0.200000 3.000000 12 13426 -0.019454 -0.118990 0.282353 -0.024089 0.639692 -1.388605
60 60 psu 2.513347 2.250847 1 5312 0.025056 0.043152 -0.260625 0.000866 -0.015723 -0.135971
61 61 psu 2.309167 2.309167 2 6842 -0.003497 0.019806 -0.318178 0.000979 0.034085 -0.338275
62 62 psu 2.379098 2.854098 3 11217 -0.027840 -0.008265 -0.162710 0.001368 0.060206 -0.334289
63 63 psu 2.648156 3.110656 4 14460 -0.028653 -0.016490 -0.045448 -0.000528 0.054414 -0.218011
64 64 psu 2.905738 3.755738 5 12563 -0.044482 -0.038341 -0.006623 -0.002655 0.075829 -0.226181
65 65 psu 3.301639 4.101639 6 11972 -0.039408 -0.040082 0.018415 0.001852 0.067869 -0.169087
66 66 psu 3.325000 4.200000 7 12086 -0.044935 -0.061934 0.037550 0.001124 0.057739 -0.164794
67 67 psu 3.436885 4.186885 8 12044 -0.035053 -0.029238 -0.047384 0.002079 0.064101 -0.192068
68 68 psu 3.104303 3.391803 9 11781 -0.020679 -0.044456 0.039928 0.000430 0.009359 -0.072102
69 69 psu 2.872541 2.897541 10 11536 -0.007394 -0.023749 0.032097 -0.001593 -0.007879 -0.008252
70 70 psu 2.766803 2.741803 11 8040 0.005847 -0.012190 -0.029995 0.001976 -0.025666 -0.002702
71 71 psu 2.650000 2.250000 12 4203 0.029873 0.046980 -0.226216 -0.003333 -0.028273 -0.067768
72 72 sxf 2.882661 2.620161 1 9831 -0.035954 -0.035623 -0.200932 0.000402 -0.109325 -0.082032
73 73 sxf 2.403333 2.803333 2 13080 -0.050724 -0.037181 -0.252375 0.001355 -0.005959 -0.389082
74 74 sxf 2.809016 3.009016 3 12690 -0.038572 -0.022772 -0.146162 -0.012631 -0.040438 -0.223680
75 75 sxf 3.003279 3.553279 4 15657 -0.053453 -0.073522 0.055782 0.002881 -0.041426 -0.121376
76 76 sxf 3.154098 3.754098 5 14180 -0.047001 -0.066916 0.080641 0.001847 -0.027178 -0.111606
77 77 sxf 3.353279 4.003279 6 18277 -0.041231 -0.081357 0.198535 -0.002596 -0.047970 -0.016045
78 78 sxf 3.437705 4.137705 7 25926 -0.030655 -0.076169 0.139599 -0.002019 -0.035586 -0.062668
79 79 sxf 3.642623 3.842623 8 24341 -0.023043 -0.051038 0.098210 -0.010316 -0.076444 0.015762
80 80 sxf 3.393443 3.393443 9 24183 -0.012663 -0.029452 0.043117 -0.000955 -0.072888 0.021908
81 81 sxf 3.195902 2.995902 10 19087 -0.005374 -0.054316 0.103311 -0.002095 -0.114805 0.161882
82 82 sxf 3.173361 2.748361 11 12388 0.005081 -0.056296 0.003005 0.002145 -0.158238 0.194702
83 83 sxf 2.924194 2.649194 12 9964 -0.020714 -0.044156 -0.295150 0.001020 -0.116622 -0.192832

84 rows × 12 columns

For each station, find the AOD scaling factor that makes the 2003 $T_L$ calculated equivalient to the static $T_L$ values.

So I did that, but in the process I made a funny discovery - the MACC AOD only results in higher $T_L$ if you include low irradiance. But, if you filter out low light conditions, then the MACC AOD calculated $T_L$ actually matches the historical values well.


In [24]:
# compare historic Linke turbidity to calculated
# downsample to monthly averages to show long term trends
f, ax = plt.subplots(2, 4, figsize=(24, 8), sharex=False)
rc = 0
for station_id, station_atm_params_3min_clear in atm_params_3min_clear.iteritems():
    r, c = rc // 4, rc % 4
    station_tl = station_atm_params_3min_clear[['lt', 'lt_calc']][is_bright[station_id]]
    station_tl['lt'].groupby(lambda x: x.month).mean().plot(linewidth=5, ax=ax[r][c])
    station_tl['lt_calc'].groupby(lambda x: x.month).mean().plot(linewidth=5, ax=ax[r][c])
    for y in xrange(2003, 2013):
        lt = station_tl['lt_calc'][('%d-01-01 00:00:00' % y):('%d-12-31 23:59:59' % y)].groupby(lambda x: x.month).mean()
        lt.plot(linestyle=':', ax=ax[r][c])
    ax[r][c].set_ylabel('$T_L$')
    ax[r][c].set_xlabel('month')
    ax[r][c].legend(['static', 'average', 'yearly'])
    ax[r][c].set_title('$T_L$ at %s' % station_id)
    ax[r][c].set_ylim([2, 6])
    rc += 1
ax[1][3].axis('off')
f.tight_layout()
plt.savefig('Linke_turbidity_allyears_monthly.png')



In [40]:
bon2003 = atm_params_3min_clear['bon'][['lt', 'lt_calc']][is_bright['bon']]['2003-01-01 00:00:00':'2003-12-31 23:59:59']
monthly_2003_tl = bon2003.resample('M').mean()
monthly_2003_tl.plot()


Out[40]:
<matplotlib.axes._subplots.AxesSubplot at 0x25505cf8>

In [41]:
mean_2003_tl = monthly_2003_tl.mean()
mean_2003_tl['lt']/mean_2003_tl['lt_calc']


Out[41]:
1.0326089681953619

In [42]:
monthly_2003_tl['scaled'] = monthly_2003_tl['lt_calc']*mean_2003_tl['lt']/mean_2003_tl['lt_calc']
monthly_2003_tl.plot()


Out[42]:
<matplotlib.axes._subplots.AxesSubplot at 0x5b453588>

In [43]:
mean_monthly_2003_tl = monthly_2003_tl['lt'] / monthly_2003_tl['lt_calc']
mean_monthly_2003_tl


Out[43]:
timestamps
2003-01-31    1.086539
2003-02-28    1.080151
2003-03-31    0.894980
2003-04-30    0.999688
2003-05-31    1.084635
2003-06-30    1.162586
2003-07-31    1.056009
2003-08-31    0.865479
2003-09-30    1.247196
2003-10-31    1.068039
2003-11-30    0.930753
2003-12-31    0.975713
Freq: M, dtype: float64

In [44]:
atm_params_2003 = atm_params_3min_clear['bon'][['amp', 'pwat', 'tau700', 'lt']][is_bright['bon']]['2003-01-01 00:00:00':'2003-12-31 23:59:59']
def _poop(x, amp=atm_params_2003['amp'], pwat=atm_params_2003['pwat'], bbaod=atm_params_2003['tau700']):
    lt_calc = pvlib.atmosphere.kasten96_lt(amp, pwat, (x * bbaod))
    lt_calc_monthly = lt_calc.resample('M').mean()
    lt_monthly = atm_params_2003['lt'].resample('M').mean()
    return np.sum((lt_calc_monthly - lt_monthly)**2)
res = minimize_scalar(_poop)
res


Out[44]:
     fun: 1.8152619564883925
    nfev: 6
     nit: 5
 success: True
       x: 1.0353899410064087

In [45]:
monthly_2003_tl['scaled_monthly'] = pvlib.atmosphere.kasten96_lt(atm_params_2003['amp'], atm_params_2003['pwat'], res['x']*atm_params_2003['tau700']).resample('M').mean()
monthly_2003_tl.plot()


Out[45]:
<matplotlib.axes._subplots.AxesSubplot at 0x61f78048>

In [46]:
solis_scaled = pvlib.clearsky.simplified_solis(
    atm_params_3min_clear['bon']['apparent_elevation'],
    atm_params_3min_clear['bon']['tau700']*res['x'],
    atm_params_3min_clear['bon']['pwat'],
    pressure=atm_params_3min_clear['bon']['press'],
    dni_extra=atm_params_3min_clear['bon']['etr']
)
solis_scaled.rename(columns={'ghi': 'scaled_ghi', 'dni': 'scaled_dni', 'dhi': 'scaled_dhi'}, inplace=True)
solis_scaled = pd.concat([solis_scaled, atm_params_3min_clear['bon'][['solis_ghi', 'solis_dni', 'solis_dhi', 'ghi', 'dni', 'dhi']]], axis=1)
solis_scaled['solis_ghi_err'] = solis_scaled['solis_ghi'] - solis_scaled['ghi']
solis_scaled['solis_dni_err'] = solis_scaled['solis_dni'] - solis_scaled['dni']
solis_scaled['solis_dhi_err'] = solis_scaled['solis_dhi'] - solis_scaled['dhi']
solis_scaled['ghi_err'] = solis_scaled['scaled_ghi'] - solis_scaled['ghi']
solis_scaled['dni_err'] = solis_scaled['scaled_dni'] - solis_scaled['dni']
solis_scaled['dhi_err'] = solis_scaled['scaled_dhi'] - solis_scaled['dhi']
solis_scaled['ghi_norm'] = solis_scaled['ghi_err']**2
solis_scaled['dni_norm'] = solis_scaled['dni_err']**2
solis_scaled['dhi_norm'] = solis_scaled['dhi_err']**2
solis_scaled_annual = solis_scaled.resample('A').mean()
solis_scaled_annual['ghi_rel'] = solis_scaled_annual['ghi_err'] / solis_scaled_annual['ghi']
solis_scaled_annual['dni_rel'] = solis_scaled_annual['dni_err'] / solis_scaled_annual['dni']
solis_scaled_annual['dhi_rel'] = solis_scaled_annual['dhi_err'] / solis_scaled_annual['dhi']
solis_scaled_annual['solis_ghi_rel'] = solis_scaled_annual['solis_ghi_err'] / solis_scaled_annual['ghi']
solis_scaled_annual['solis_dni_rel'] = solis_scaled_annual['solis_dni_err'] / solis_scaled_annual['dni']
solis_scaled_annual['solis_dhi_rel'] = solis_scaled_annual['solis_dhi_err'] / solis_scaled_annual['dhi']
solis_scaled_annual[['ghi_rel', 'dni_rel', 'solis_ghi_rel', 'solis_dni_rel']].plot()


Out[46]:
<matplotlib.axes._subplots.AxesSubplot at 0x5f4b6358>

So, as it turns out, scaling the MACC AOD isn't necessary, or at least the magnitude of MACC AOD isn't an issue. And it doesn't seem to be too high.


In [ ]: